﻿using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
namespace System.Net
{
    public partial class HttpListenerHost : IDisposable
    {
        private HttpListener _listener = (HttpListener.IsSupported ? new HttpListener() : null);
        private Thread _connectionManagerThread;
        private bool _disposed;
        private long _runState = (long)State.Closed;
        private Type _workerRequestType;
        private static RequestTimeoutManager _timeoutManager;
        private static int _activeHosts;
        private static bool _useThreading;
        internal int _activeRequests;

        public enum State
        {
            Closed,
            Closing,
            Opening,
            Opened
        }

        static HttpListenerHost()
        {
            ExecutionTimeout = new TimeSpan(0, 3, 0);
        }

        public static TimeSpan ExecutionTimeout { get; set; }
        public static bool IsDebuggingEnabled { get; set; }

        public static bool UseThreading
        {
            get { return _useThreading; }
            set
            {
                if (_activeHosts != 0)
                    throw new InvalidOperationException("Cant_Set_While_Any_Open");
                _useThreading = value;
            }
        }

        protected HttpListenerHost() { }
        public HttpListenerHost(Type workerRequestType)
            : this(workerRequestType, null) { }
        public HttpListenerHost(Type workerRequestType, string uriPrefix)
        {
            if ((workerRequestType != null) && !typeof(HttpListenerWorkerRequest).IsAssignableFrom(workerRequestType))
                throw new ArgumentOutOfRangeException("workerRequestType", "must be of type HttpWorkerRequest");
            if (_listener == null)
                return;
            _workerRequestType = workerRequestType;
            if (!string.IsNullOrEmpty(uriPrefix))
                _listener.Prefixes.Add(uriPrefix);
        }

        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)
            {
                if (RunState != State.Closed)
                    Close();
                if (_connectionManagerThread != null)
                {
                    _connectionManagerThread.Abort();
                    _connectionManagerThread = null;
                }
            }
            _disposed = true;
        }

        public HttpListener Listener
        {
            get { return _listener; }
        }

        public State RunState
        {
            get { return (State)Interlocked.Read(ref _runState); }
        }

        internal static RequestTimeoutManager RequestTimeoutManager
        {
            get { return _timeoutManager; }
        }

        #region Threading

        public virtual void Open()
        {
            if (_listener == null)
                return;
            if (_listener.IsListening)
                throw new InvalidOperationException("Already opened.");
            Interlocked.Increment(ref _activeHosts);
            try { _listener.Start(); }
            catch (HttpListenerException ex)
            {
                if (!ex.Message.Contains("Access is denied"))
                    throw;
                _listener = null;
                return;
            }
            _timeoutManager = new RequestTimeoutManager();
            // create thread for listening and block till its in opened state
            _connectionManagerThread = new Thread(new ThreadStart(ConnectionManager)) { Name = "Connection Manager: " + Guid.NewGuid() };
            _connectionManagerThread.Start();
            long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
            while (RunState != State.Opened)
            {
                Thread.Sleep(100);
                if (DateTime.Now.Ticks > waitTime)
                    throw new TimeoutException("Unable to start the request handling process.");
            }
        }

        public virtual void Close()
        {
            if (_listener == null)
                return;
            if (!_listener.IsListening)
                throw new InvalidOperationException("Already closed.");
            Interlocked.Exchange(ref _runState, (long)State.Closing);
            _listener.Stop();
            //
            long waitTime = DateTime.Now.Ticks + TimeSpan.TicksPerSecond * 10;
            while (RunState != State.Closed)
            {
                Thread.Sleep(100);
                if (DateTime.Now.Ticks > waitTime)
                    throw new TimeoutException("Unable to stop the web server process.");
            }
            _timeoutManager.Stop();
            Interlocked.Decrement(ref _activeHosts);
            _connectionManagerThread.Abort(); _connectionManagerThread = null;
        }

        private void ConnectionManager()
        {
            Interlocked.Exchange(ref _runState, (long)State.Opening);
            try
            {
                Interlocked.Exchange(ref _runState, (long)State.Opened);
                try
                {
                    if (UseThreading)
                        InitRequestQueue();
                    Console.WriteLine("Web:Opened");
                    while (RunState == State.Opened)
                    {
                        var context = _listener.GetContext();
                        //Console.WriteLine("Web:Request");
                        var wr = CreateWorkerRequest(context);
                        ProcessRequestNoDemand(wr);
                    }
                }
                // This will occur when the listener gets shut down. Just swallow it and move on.
                catch (HttpListenerException) { }
            }
            finally { Interlocked.Exchange(ref _runState, (long)State.Closed); }
            //
            int totalSeconds = 90;
            WaitForRequestsToFinish(totalSeconds * 0x3e8);
            if (_requestQueue != null)
                _requestQueue.Drain();
            WaitForRequestsToFinish((totalSeconds * 0x3e8) / 6);
        }

        private void WaitForRequestsToFinish(int waitTimeoutMs)
        {
            var time = DateTime.UtcNow.AddMilliseconds((double)waitTimeoutMs);
            do
            {
                if (_activeRequests == 0)
                {
                    if (!UseThreading)
                        return;
                    if (_requestQueue == null)
                        break;
                    if (_requestQueue.IsEmpty)
                        return;
                }
                Thread.Sleep(250);
            }
            while (Debugger.IsAttached || (DateTime.UtcNow <= time));
        }

        #endregion

        #region ProcessRequest

        internal RequestQueue _requestQueue;

        private void InitRequestQueue()
        {
            _requestQueue = new RequestQueue(this, 5, 5, 3, new TimeSpan(0, 0, 30));
        }

        protected virtual HttpListenerWorkerRequest CreateWorkerRequest(HttpListenerContext context)
        {
            return ((HttpListenerWorkerRequest)Activator.CreateInstance(_workerRequestType, context));
        }

        internal void ProcessRequestNoDemand(HttpListenerWorkerRequest wr)
        {
            if (!UseThreading)
            {
                Parallel.Invoke(() => ProcessRequestNow(wr));
                return;
            }
            var queue = _requestQueue;
            if (queue != null)
                wr = queue.GetRequestToExecute(wr);
            if (wr != null)
                ProcessRequestNow(wr);
        }

        internal virtual void ProcessRequestNow(HttpListenerWorkerRequest wr) { }

        internal virtual void RejectRequestNow(HttpListenerWorkerRequest wr, bool silent) { }

        #endregion
    }
}
